/*
 * @file iso9660_init.c
 *
 *    ISO9660 file operation table.
 */


#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/seterr.h>

#include "iso9660.h"
#include "iso9660_io.h"
#include "iso9660_record.h"
#include "iso9660_handlers.h"
#include "iso9660_susp.h"
#include "iso9660_dbg.h"


/*
 *   Handler table for iso9660 file nodes
 */
const rtems_filesystem_file_handlers_r ISO9660_file_handlers = {
  rtems_iso9660_file_open,
  rtems_iso9660_file_close,
  rtems_iso9660_file_read,
  rtems_iso9660_file_write,
  rtems_filesystem_default_ioctl,
  rtems_filesystem_default_lseek,
  rtems_iso9660_file_fstat,
  rtems_filesystem_default_fchmod,
  rtems_filesystem_default_ftruncate,
  rtems_filesystem_default_fpathconf,
  rtems_filesystem_default_fsync,
  rtems_filesystem_default_fdatasync,
  rtems_filesystem_default_fcntl,
  rtems_filesystem_default_rmnod
};

#define ISO9660_FILE_ID_SEPARATOR_1 '.'
#define ISO9660_FILE_ID_SEPARATOR_2 ';'

int
iso9660_clean_identifier_length(
    iso9660_fs_info_t    *fs,
    char                *id,
    size_t               id_len
)
{
  char *pch;
  int version_separator, extension_separator;

  if (rtems_iso9660_section_traced(RTEMS_ISO9660_DEBUG_FILENAME))
    rtems_iso9660_dbg_string(id, id_len);

  /* No transformation is Joliet is enabled */
  if (iso9660_version_supports(fs->version, ISO9660_EXTENSION_JOLIET)
      && iso9660_version_supports(fs->version_mask, ISO9660_EXTENSION_JOLIET))
    return id_len;

  /* No transformation if filename mapping is disabled */
  if (fs->mount_options.map == ISO9660_MOUNT_MAP_DISABLED)
    return id_len;

  if (fs->mount_options.map == ISO9660_MOUNT_MAP_LOWERCASE)
  {
    int c;

    for (c = 0; c < id_len; c++)
      id[c] = tolower((int)id[c]);
  }

  /* Does the source filename is of form file.ext;version ? */
  pch = strchr(id, ISO9660_FILE_ID_SEPARATOR_2);

  /*  Ill formatted ISO9660 filename or directory-name,
   * we just do a classic comparison
   */
  if (pch == NULL)
     return id_len;
  version_separator = pch-id;

  pch = strchr(id, ISO9660_FILE_ID_SEPARATOR_1);
  if (pch == NULL)
    return id_len;
  extension_separator = pch-id;

  /* Ill formatted ISO9660 filename */
  if (extension_separator > version_separator)
    return id_len;

  /* Filename without extension : file.;version,
   * strip the version and the extension dot
   */
  if (extension_separator == version_separator - 1)
    return extension_separator;

  /* Complete ISO9660 filename : file.ext;version, strip the version */
  return version_separator;
}


/**
 *  This method defines the interface to the default open(2)
 *  system call support which is provided by a file system
 *  implementation.
 */
int
rtems_iso9660_file_open(
    rtems_libio_t   *iop,
    const char     *pathname,
    uint32_t         flag,
    uint32_t         mode
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);
  int                 rc;

  if (record->flags.directory)
    rtems_iso9660_error_and_return_minus_one(EISDIR,
        "file_open: Given record is a directory.");

  rc = iso9660_record_open(record);
  iop->offset = 0;

  return rc;
}


/**
 *  This method defines the interface to the default close(2)
 *  system call support which is provided by a file system
 *  implementation.
 */
int
rtems_iso9660_file_close(
    rtems_libio_t *iop
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);

  iso9660_record_close(record);
  return 0;
}


ssize_t
rtems_iso9660_file_read(
    rtems_libio_t   *iop,
    void            *buffer,
    size_t           count
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_iop(iop);
  ssize_t             bytes = 0, to_read;
  int                 rc;

  /* Calculate the max. amount of data to copy */
  to_read = MIN(count, record->size - iop->offset);

  while (to_read > 0)
  {
    /* # of bytes to copy this pass */
    size_t offset = record->cache->offset - iop->offset,
        remaining_in_buffer = record->cache->count + offset,
        i = MIN(to_read, remaining_in_buffer);

    if (i <= 0)
      break;

    /* Load next sector in memory if needed */
    if (remaining_in_buffer == 0)
    {
      rc = iso9660_read_sector(record->fs, record->cache->sector+1, 0,
          ISO9660_SECTOR_SIZE, record->cache->buffer);

      if (rc != ISO9660_SECTOR_SIZE)
      {
        char buffer[128];
#if RTEMS_ISO9660_DEBUG
        snprintf(buffer, 127,
            "file_read: Failed to read sector %" PRIu32 ".",
            (uint32_t)record->cache->sector+1);
#endif

        rtems_iso9660_error_and_return_minus_one(EISDIR, buffer);
      }
    }

    memcpy((unsigned char*)buffer+bytes, record->cache->buffer + offset, i);

    to_read -= i;
    bytes += i;
    iop->offset += i;
  }

  return bytes;
}


ssize_t
rtems_iso9660_file_write(
    rtems_libio_t   *iop,
    const void     *buffer,
    size_t           count
)
{
  rtems_set_errno_and_return_minus_one(ENOSYS);
}



int
rtems_iso9660_file_fstat(
    rtems_filesystem_location_info_t  *pathloc,
    struct stat*                      buf
)
{
  iso9660_record_t   *record = rtems_iso9660_get_record_pathloc(pathloc);

  if (record == NULL)
    rtems_iso9660_error_and_return_minus_one(ENOENT,
        "file_fstat: Failed to retrieve record from pathloc.");

  return iso9660_record_stat(record, buf, 0);
}
